#
#   Copyright (C) 2007 Veit Wehner.
#   All Rights Reserved.
# 
#   This is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
# 
#   This software is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
# 
#   You should have received a copy of the GNU General Public License
#   along with this software; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
#   USA.
# 


#!/usr/bin/env python
## \file update_tool.py
## \brief The Frontend.
## This script implements the frontend 
## of the remote update functionality.
##
# Its main features are:
# - generating a memory image from an elf file
# - transmitting a memory image via UART or ethernet to the injector node
# - transmit different remote control commands to the injector node
# - setup a secret key to the information memory 
#  ( implicit CRC adjustment afterwards )
#  
# Please watch the command line options for more details.


import time
import sys, getopt, string
from copy import copy
import ctypes
import memory
from snow5 import MSPJTAG, InfoMem
from optparse import OptionParser, Option, OptionValueError

def check_autoint(option, opt, value):
  try:
    return int(value.strip(), 0)
  except TypeError:
    raise OptionValueError("option %s: not a number: %r" % (opt, value))

class MyOption(Option):
  TYPES = Option.TYPES + ("autoint", )
  TYPE_CHECKER = copy(Option.TYPE_CHECKER)
  TYPE_CHECKER["autoint"] = check_autoint

## Configuration of the communication 
#
# between the machine running this script 
# and the sensor node
uart = 1
ethernet = 0

## Configuration of update security 
## kind security influences the packet length
## As TEA only works on blocks of 8 byte, the 
## Injector has to fill up the packet with zeros
## to a multiple of eight. Then packetlength itself
## ( in YaNETBuffer ) is not equal to the real data length. 
## Therefore chunksize is reduced by one and the last byte 
## hold the real length. 
## 
## If the injector uses the Davies-Meyer Algorithm 
## to generate a cryptographic checksum from TEA, 
## an eight byte signature is added as packet suffix.

ENCRYPTION = 0
CRYPTOGRAPHIC_CHECKSUM = 0

## These are the remote control commands. 
## They are quite orthogonal and 
## minimalistic. Please refer to the manual or 
## the command line help for more information
UPDATE = 50
JUST_STORE_AN_IMAGE = 51
RESTORE_AND_EXECUTE_AN_IMAGE = 52
SETUP_INFOMEM = 0
## If a node needs to be reprogrammed,
## one file is specified to store 
## the image header information extracted from 
## the ELF file and transmitted afterwards. 
## The other file will hold the binary data of the memory 
## imager. 
## 
## Otherwise, if an existing image ( existing on the 
## external flash of a node ) is to be restored, the 
## files point to existing files. Then, no program image 
## will be transmitted.  
FILE_FOR_IMAGE_HEADER = -1#10
FILE_FOR_SEGMENT_DATA = -1#11

#SEND_SEGMENT_DATA = 1
SRC_ID  = -1#0#x011a
DEST_ID = -1#0x011a
#recovery_packet= ""
packet = ""
packet_size = 32
UPDATE_COMMAND = -1#50#JUST_STORE_AN_IMAGE 

## Configure the communication betweeen this machine and 
## the injector node
if (uart):
  from mspgcc import serial
  ser = serial.Serial('/dev/ttyS0')
  ser.baudrate=115200
  ser.bytesize=8
if (ethernet):  
  import socket
  socke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


def set_packet_size(options):
## Adjust the packet size to the checksum or cryptographic 
# requirements.  
  global packet_size
  if (options.security == None):  
    packet_size = 32
  elif (options.security == "TEA"):
    packet_size = 31
  elif (options.security == "Davies-Meyer"):
    packet_size = 24
    

## Instances for accessing the device through JTAG
msp = MSPJTAG()
imem  = InfoMem()
size = 256

## CRC Polynoms
crc16_init = 0xFFFF
crc16_poly = 0x1021

def usage():
## print the help message

    print '''
     This script implements the frontend 
     of the remote update functionality.
    
     Its main features are:
     - generating a memory image from an elf file
     - transmitting a memory image via UART or ethernet to the injector node
     - transmit different remote control commands to the injector node
     - setup a secret key to the information memory 
      ( implicit CRC adjustment afterwards )
      
     Please watch the command line options for more details.
      (python) update_tool.py $COMMAND [options]
    
        %Supported Commands are:
            a) UPDATE
            b) ADDIMAGE
            c) SETUP-SECRET 
        
    (python) update_tool.py --help for options
    
    EXAMPLES: 
      python update_tool.py UPDATE -f "ledtest.elf" -s 0x0000 -d 0x011a --header-file 1 --segment-file 2 
      python update_tool.py UPDATE -f "ledtest.elf" -s 0x0000 -d 0x011a --header-file 1 --segment-file 2 --security-level DAVIES-MEYER
      python update_tool.py SETUP-SECRET
    '''
    sys.exit(1)
    
def init_device():
## This method opens 
#  a JTAG connection to device
#  and downloads the contents from 
#  the information memory.
#  It is needed for setting up a new key
#  ( when encryption or signature is activated )
    msp.open()
    msp.connect()
    msp.reset()
    imem.setContents(msp.getInfoMem())
        
def wr_and_release_device():
## This method writes the new information 
#  memory block back to the device
    msp.setInfoMem(imem.getData())
    msp.writeFlash()
    msp.reset(1, 1)
    msp.close()
        
def set_password():
## This method writes the a password 
#  to the information memory object 
#  ( on the computer ). 
#  The content of this object will be written back 
#  to the device in the wr_and_release_device method
    password = [0x00, 0x00, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 
            0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77]
    add = 128
    for b in password:
         imem.setByte(add, b)
         add = add +1 
              
def setup_infomem():
## This method connects to the 
#  device via JTAG, erases the second segment 
#  in the information memory ( containing only 
#  the CRC actually ). 
#  The secret key is written into infomem, the CRC 
#  is adjusted and the device released.
         init_device()
         msp.makeActionSegmentErase(0x1080)()
         set_password()
         imem.update_crc()
         wr_and_release_device()
                        
                        
def string_to_array(str):
## This method delivers the byte representation 
#  of a string object. 
#  @param str String object.
         mybuf = []
         for s in str:
            mybuf.append (ord (s))
         return mybuf
         
         
                            
def calc_crc(data, len=packet_size):
                  crc = crc16_init
                  for w in range(0, (len/2) - 1):      
                    data_word = (data[(w*2)+1] << 8) + (data[(w*2)+0] << 0)
                    print("CRC 0x%04X:0x%04X - 0x%04X" % (0x1000 + w*2, data_word, crc))
                  for b in range(0, 16):
                            crc <<= 1;
                            crc |= (data_word & 1);
                            if(crc & 0x10000):
                                crc ^= crc16_poly
                                crc&=0xFFFF
                                data_word >>= 1
                                crc ^= 0xFFFF
                  return crc
              
    
def send_packet():
## This methods transmits a packet via UART or Ethernet
           if (uart):
             ser.write(packet)
           elif (ethernet):
             socke.send(packet)
                      
def write_int_as_string(int_value):	
## This method constructs a string from 
#  the given value in integer representation. 
#  UART and Ethernet both transmit streams of bytes.
#  @param int_value Integer object.
           global packet
           first_byte = chr(int_value & 0xff)
           sec_byte = chr((int_value>>8)&0xff)
           packet = first_byte + sec_byte
           send_packet()
          
def wait(i=None):
## This method waits for a synchronization packet 
#  from the injector node. 
#  Furthermore checks the content of the sync packet. 
#  In case of a negative sync signal, the packet will be 
#  transmitted again, unless the number of configured retries is reached.
#  @param i just a debug information.
  global packet
  #print (i)
  if uart:
    sig = ser.readline()
    print "Received Sync Signal: " + sig
    if (sig == "encore une foi\n"):
       send_packet()
       wait()
    
  elif ethernet:
    socke.recv(1)
    print (i)

    
def send_image(imagefile):
## This method transmits the segments from 
#  the memory image ( is not the same as the elf image ) 
#  to the injector node.
#  @param imagefile File in local directory containing the elf file.

       time.sleep(0.5)
       global packet
       elf_image = memory.Memory()          
       elf_image.loadFile(imagefile)
       number_of_segments = len(elf_image.segments)
       wait()
       write_int_as_string(number_of_segments)
       for s in elf_image.segments:
           wait()
           write_int_as_string(s.startaddress)
           wait()
           write_int_as_string(s.__len__())
           i = 0
           packet=""
           for data in s.data:
               i = i +1 
               packet = packet + data
               if (i%packet_size==0): 
                   wait(i%packet_size)
                   send_packet()
                   packet = ""
           if packet != "":
               wait(i%packet_size)
               send_packet()
               packet = ""


def main():

## First of all, the command line options are examined. 
#  Afterwards connections to a node might be established 
#  ( via JTAG, UART or Ethernet )
#  Then the remote controll command is processed.
# python snow5-coin.py --node-id=26 --cc1100=../include/config/cc1100.default.433.dat -C
# python update_tool.py UPDATE -f "ledtest.elf" -s 0 -d 26 --header-file 1 --segment-file 2 

       try: 
       
           command = sys.argv[1]
           if (command == "setup-secret"):
               setup_infomem()
               sys.exit(1)
           elif command == "UPDATE":
               global UPDATE_COMMAND
               UPDATE_COMMAND = UPDATE
           elif command == "ADDIMAGE":
               UPDATE_COMMAND = ADDIMAGE
       except: 
           usage()
           sys.exit(0)
       op 	= OptionParser(option_class=MyOption)
       
       file_help = " If a node needs to be reprogrammed,\
                     one file is specified to store \
                     the image header information extracted from \
                     the ELF file and transmitted afterwards. \
                     In the other file the binary data of the memory \
                     image shall be stored later. \
                       \
                     Otherwise, if an existing image ( existing on the \
                     external flash of a node ) is to be restored, the \
                     files point to existing files. Then, no program image \
                     will be transmitted."  
       
       op.add_option("-f", "--file", type="string", dest="elf", default="ledtest.elf",
                  help="specify the ELF File [default=%default]")
       op.add_option("-s", "--src", type="autoint", dest="src", default=None,
                  help="set the id of the source node [default=%default]")
       op.add_option("-d", "--dest", type="autoint", dest="dest", default=None,
                  help="set the id of the destination node [default=%default]")           
       op.add_option("--header-file", type="int", dest="header_file", default=1,
                  help=file_help + "set the file number for the file on the external flash wich should hold the transmitted image header [default=%default]")
       op.add_option("--segment-file", type="int", dest="segment_file", default=None,
                  help="set the file number for the file on the external flash wich should hold the transmitted segment data [default=%default]")
       op.add_option("-c", "--connection", type="string", dest="con", default="uart",
                  help="set the type of connection to the INJEKTOR node - ethernet and uart supported [default=%default]")
       op.add_option("--security-level", type="string", dest="security", default="NO",
                  help="specify the security level for Communication in the Sensor Network - TEA Encryption and Cryptographic Checksum by Davies-Meyer are supported [default=%default]")
                  
       (options, args) = op.parse_args()
       
       if options.src != None:
           print("Setting source node id to 0x%04x" % (options.src))
           global SRC_ID
           SRC_ID = options.src
       if options.dest != None:
           print("Setting destination node id to 0x%04x" % (options.dest))
           global DEST_ID
           DEST_ID = options.dest
       if options.header_file != None:
           print("Setting the file number for the file on the external flash wich should hold the transmitted image header to %i" % options.header_file)
           global FILE_FOR_IMAGE_HEADER
           FILE_FOR_IMAGE_HEADER = options.header_file
       if options.header_file != None:
           print("Setting the file number for the file on the external flash wich should hold the transmitted segment data to %i" % (options.segment_file))
           global FILE_FOR_SEGMENT_DATA
           FILE_FOR_SEGMENT_DATA = options.header_file
       if options.security != None:
           print("Setting security level to %s" % (options.security))
       if options.src != None:
           print("Setting source node id to 0x%04x" % (options.src))
           
       set_packet_size(options)   
          
       
       
       if ethernet:
         socke.connect(('132.187.13.200', 40))    
         wait()
         
       write_int_as_string(UPDATE_COMMAND)
       wait()
       write_int_as_string(options.src)
       wait()
       write_int_as_string(options.dest)
       if ((options.src == 0)or(options.src == options.dest)):
         wait()
         write_int_as_string(options.header_file)  
         wait()
         write_int_as_string(options.segment_file)
       if (options.src == 0):
         send_image(options.elf)  
       if uart:
         ser.close()
       elif ethernet:
         socke.close()
               
                           
                           
if __name__ == '__main__':
## This method is called when the script is called directly
#  from the command line. This is the normal case. Its not 
#  designed to be called from within another python script.
#
                               try:
                                   main()
                               except SystemExit:
                                   raise                                        
                               except KeyboardInterrupt:
                                   sys.stderr.write("User abort.\n")               
                                   sys.exit(1)                                     
                                   
